#include "CPU.h"

program* CPU_assemble(FILE* f)
{
    program* prgr = (program*) calloc(1, sizeof(program));
    prgr = assemble(f);
    return prgr;
}
int CPU_run(struct program* prgr)
{
    if(prgr == NULL)
        return EXIT_FAILURE;
    struct CPU* cpu =(CPU*) calloc(1, sizeof(cpu));
    list_t* functionStack = (list_t*) calloc(1,sizeof(list_t));
    list_ctor(functionStack);

    int current_command = 0,
        err = 0;
    double value = 0,
           value1 = 0,
           value2 = 0;
    int temp = 0;
    cpu->prgr = prgr;
    stack_ctor(&cpu->stk);

    FILE* f_out = NULL;
    if (!(f_out = fopen("output.txt", "w+")))
    {printf("Unable to open output"); abort();}

    while(current_command < cpu->prgr->length)
    {
        switch(cpu->prgr->com[current_command].command)
        {
            case MOV:
                cpu->prgr->var[cpu->prgr->com[current_command].valueType].value =
                    cpu->prgr->com[current_command].value;
                break;
            case PUSH:
                CPU_push(cpu, current_command);
                break;

            case POP :
                CPU_pop (f_out, cpu, current_command);
                break;

            case ADD:
                value  = stack_pop(&cpu->stk, &err);
                value += stack_pop(&cpu->stk, &err);
                stack_error(&cpu->stk, stack_push(&cpu->stk, value));
                break;

            case JE:
                value1 = stack_pop(&cpu->stk, &err);
                value2 = stack_pop(&cpu->stk, &err);

                if (value1 == value2)
                    current_command = cpu->prgr->labels[(int)cpu->prgr->com[current_command].value].position
                    -1;
                break;

            case JA:
                value1 = stack_pop(&cpu->stk, &err);
                value2 = stack_pop(&cpu->stk, &err);

                if (value1 < value2)
                    current_command = cpu->prgr->labels[(int)cpu->prgr->com[current_command].value].position
                    -1;
                break;

            case JB:
                value1 = stack_pop(&cpu->stk, &err);
                value2 = stack_pop(&cpu->stk, &err);

                if (value1 > value2)
                    current_command = cpu->prgr->labels[(int)cpu->prgr->com[current_command].value].position
                    -1;
                break;

            case MUL:
                value  = stack_pop(&cpu->stk, &err);
                value *= stack_pop(&cpu->stk, &err);
                stack_error(&cpu->stk, stack_push(&cpu->stk, value));
                break;

            case DIVI:
                value  = stack_pop(&cpu->stk, &err);
                value1 = stack_pop(&cpu->stk, &err);

                if (value1) value /= value1;
                else {printf("Div 0"); abort();}

                stack_error(&cpu->stk, stack_push(&cpu->stk, value));
                break;

            case SUB:
                value  = stack_pop(&cpu->stk, &err);
                value -= stack_pop(&cpu->stk, &err);
                stack_error(&cpu->stk, stack_push(&cpu->stk, value));
                break;

            case SQRTI:
                value = stack_pop(&cpu->stk, &err);
                value = sqrt(value);
                stack_error(&cpu->stk, stack_push(&cpu->stk, value));
                break;

            case DUB:
                value = stack_pop(&cpu->stk, &err);
                stack_error(&cpu->stk, stack_push(&cpu->stk, value));
                stack_error(&cpu->stk, stack_push(&cpu->stk, value));
                break;
            case CALL:
                list_insertA(functionStack, functionStack->lastNode, current_command + 1);
                current_command = (int) cpu->prgr->com[current_command].value - 1;
                break;
            case FUNC:
                while(cpu->prgr->com[current_command].command != SKIP)
                    current_command++;

                if(current_command > cpu->prgr->length)
                {
                    printf("ERROR: AFTER FUNCTION USE <skip>");
                    return 0;
                }
                break;
            case SKIP:
                if(functionStack->lastNode)
                {
                    current_command = functionStack->lastNode->value - 1;
                    list_delete(functionStack, functionStack->lastNode);
                }
                break;
            case CALLA:
                value1 = stack_pop(&cpu->stk, &err);
                value2 = stack_pop(&cpu->stk, &err);

                if (value1 < value2)
                {
                    list_insertA(functionStack, functionStack->lastNode, current_command + 1);
                    current_command = (int) cpu->prgr->com[current_command].value - 1;
                }
                break;
            case 1:
                break;

            default:
                printf("smth goes wrong");
                break;
        }
        current_command++;
        printf("\n<%d>\n", current_command);
        list_dump(functionStack);
    }
    printCommands(cpu->prgr);
    //stack_bump(&cpu->stk);
    stack_dtor(&cpu->stk);
    fclose(f_out);
    free(cpu);
}
int CPU_push(struct CPU* cpu, int curCom)
{
    assert(cpu);
    int n = cpu->prgr->com[curCom].valueType;
    double value = cpu->prgr->com[curCom].value;
    if(n == NUMBER)
        stack_push(&cpu->stk, value);
    else
        stack_push(&cpu->stk, cpu->prgr->var[(int)value].value);
    return 0;
}
int CPU_pop(FILE* f_out, CPU* cpu, int curCom)
{
    assert(cpu);
    assert(f_out);
    assert(cpu->prgr);
    int err;
    if(cpu->prgr->com[curCom].valueType == 0)
    {
        fprintf(f_out, "%lg\n", stack_pop(&cpu->stk, &err));
    }
    else
        cpu->prgr->var[(int)cpu->prgr->com[curCom].value].value =
            stack_pop(&cpu->stk, &err);
    stack_error(&cpu->stk, err);
    return 0;
}
